#include "Parser.h"

Lista<string>* Parser::closingTags=NULL;
ArchivoLecturaSecuencial* Parser::xml=NULL;


Tag* Parser::parse(string xmlPath){
			
	closingTags = new Lista<string>();
	ComparadorString* comparer = new ComparadorString();
	closingTags->setComparador(comparer);

	Tag* rootFather = new Tag();
	xml = new ArchivoLecturaSecuencial(xmlPath);
	
	appendChildren(xml,rootFather);

	IteradorLista<string> itClosingTags = closingTags->iterador();
	while(itClosingTags.tieneSiguiente()){
		Logger::getInstance()->warn("El tag " + (*itClosingTags.siguiente()) + " no fue cerrado.");
	}

	//Si mato el comparer pincha al matar la lista.
	//delete comparer;
	xml->close();
	delete closingTags;
	delete xml;

	rootFather->setTipo("Document");
	return rootFather;
	

	
}


void Parser::appendChildren(ArchivoLecturaSecuencial* xml, Tag* father){
	
	string* header = new string();

	bool esAutocontenido = extraerEncabezado(xml, header);
	
	while(!isClosingTag(header) && !xml->eof()){
		
		Tag* child = new Tag();
		cargarAtributos(child, header, esAutocontenido);
		
		if(!esAutocontenido){
			appendChildren(xml, child);
		}
		
		father->addHijo(child);

		esAutocontenido = extraerEncabezado(xml, header);
	}

}

/*
 * Chequea si el header pasado es un tag de cierre. Si es asi verifica que sea el 
 * tag de cierre que corresponde mediante la lista, de lo contrario anota un warning.
 */
bool Parser::isClosingTag(string* header){
	
	char simbolos[1];
	simbolos[0] = ' ';
	int index = 0;

	IteradorLista<string> it = closingTags->iterador();
	escapar(simbolos,1,index,header);
	index++;
	if(header->length() > index  && header->at(index ) == '/'){
		index++;
		string tagName = "";
		while(header->length() > index && header->at(index) != ' ' ){
			tagName += header->at(index);
			index++;
		}
		
		/*
		 * Desapilo los tags habierto hasta encontrar el que esta siendo cerrado.
		 */
		bool encontrado = false;
		
		int indice = 0;
		IteradorLista<string> i =  closingTags->iterador();
		while(i.tieneSiguiente()){
			i.siguiente();
			indice++;
		}
		int tamanioLista = indice;
		indice--;

		while(indice >= 0 && !encontrado){
			
			it.seek(indice);
			string tagCierre = *it.getActual();
			if(tagCierre == tagName){
				encontrado = true;	
				closingTags->quitar(&tagName);
				tamanioLista--;
			}
			indice--;
			
		}
		if(!encontrado){
			stringstream s;
			s << " Linea: " << xml->getNumeroLinea() << ". " << "El tag de cierre " << tagName << " nunca fue abierto.";
			Logger::getInstance()->warn(s.str());
		}else{
			if(!closingTags->estaVacia() && indice >= 0) popClosingTagsUpTo(indice);	
		}
		return true;
	}
	return false;
}

void Parser::popClosingTagsUpTo(int indicePrimero){
	IteradorLista<string> it = closingTags->iterador();
	it.seek(indicePrimero);

	if(it.tieneSiguiente())it.siguiente();
	while(it.tieneSiguiente()){
		string tag = *it.siguiente();
		stringstream s;
		s << " Linea: " << xml->getNumeroLinea() << ". " << "El tag " << tag << " no fue cerrado.";
		Logger::getInstance()->warn(s.str());
		closingTags->quitar(&tag);	
	}
}

/*
 * Extrae el string con el encabezado. Devuelve true si tag termina en el encabezado (cerrado son '/>').
 */
bool Parser::extraerEncabezado(ArchivoLecturaSecuencial* xml, string* atributos){
	
	char simbolos[5];
	char tubo[2] = {' ' , ' '};
	simbolos[0] = '<';
	escaparHasta(xml, simbolos, 1);

	while(!(xml->eof()) && !(tubo[0] == '/' && tubo[1] == '>') && tubo[0] != '>'){

		if(tubo[0] != '\t')(*atributos) += tubo[0];
		tubo[0] = tubo[1];
		if(tubo[0] != '>') tubo[1] = xml->siguienteCaracter();

	}

	if(tubo[0] != '>' && tubo[0] != '/'){
		(*atributos) += tubo[0];
	}

	if(tubo[0] == '/' && tubo[1] == '>'){
		return true;
	}

	return false;
}

string Parser::TipoPalabraToString(TipoPalabra tipo){
	
	if(tipo == NAME){
		return "El siguiente atributo carece de un valor: ";
		}else{
			if(tipo == VALUE){
				return "El siguiente valor no pertenece a ningun atributo valido: ";
				}else{
					return "El siguiente simbolo no pertenece a ningun atributo valido: ";
				}
		}
	
}

/*
 * Posiciona el lector en el siguiente caracter a alguno de los simbolos o al final del archivo.
 */
void Parser::escaparHasta(ArchivoLecturaSecuencial* archivo, char simbolos[] ,int cantidadSimbolos){
	
	bool hayCoincidencia = false;
	char c;
	while( !archivo->eof()  &&  !hayCoincidencia){
		c = archivo->siguienteCaracter();
		for(int i = 0; i < cantidadSimbolos; i++){
			if(c == simbolos[i] ){
				hayCoincidencia = true;
			}
		}
	}

}

void Parser::cargarAtributos(Tag* tag, string* atributos,bool esAutocontenido){
	
	Lista<string>* palabras = extraerPalabras(atributos);
	int cantidadPalabras = palabras->tamanio();
	string* pal = new string[cantidadPalabras];
	IteradorLista<string> it = palabras->iterador();
	int i = 0;

	while(it.tieneSiguiente()){
		pal[i] = *it.siguiente();
		i++;
	}


	// Coloca el tipo de Tag. Lo agrega al ppio lista de tags habiertos si no es autocontenido.
	if(cantidadPalabras > 0){
		tag->setTipo(pal[0]);
		tag->setLinea(xml->getNumeroLinea());
		if(!esAutocontenido)closingTags->agregar(new string(pal[0]));
	}

	i = 1;
	
	while(i + 2 < cantidadPalabras){
		
		if(clasificarPalabra(pal[i]) == NAME && clasificarPalabra(pal[i + 1]) == SEPARATOR && clasificarPalabra(pal[i + 2]) == VALUE){
			quitarComillas(pal[i]);
			quitarComillas(pal[i + 2]);
			tag->setAtributo( pal[i], pal[i + 2]);
			i = i + 3;
		}else{
			stringstream s;
			s << " Linea: " << xml->getNumeroLinea() << ". " << TipoPalabraToString(clasificarPalabra(pal[i])) << "  " << pal[i];
			Logger::getInstance()->warn(s.str());
			i++;
		}
	}
	
	if(i < cantidadPalabras){
		stringstream s;
		stringstream c;
		s << " Linea: " << xml->getNumeroLinea() << ". " << TipoPalabraToString(clasificarPalabra(pal[i])) << "  " << pal[i];
		Logger::getInstance()->warn(s.str());
		
		if(cantidadPalabras - i == 2){
			c << " Linea: " << xml->getNumeroLinea() << ". " << TipoPalabraToString(clasificarPalabra(pal[i + 1])) << "  " << pal[i];
			Logger::getInstance()->warn(c.str());
		}

	}
	delete[] pal;
}

void Parser::quitarComillas(string& palabra){
	
	if(palabra.length() > 0 && palabra.at(0) == '"'){
		palabra.replace(0,1,"");
	}
	if(palabra.length() > 0 && palabra.at(palabra.length() - 1) == '"'){
		palabra.replace(palabra.length() - 1,1,"");
	}
}

TipoPalabra Parser::clasificarPalabra(string palabra){
		if(palabra == "="){
			return SEPARATOR;
		}else{
			if(palabra.at(0) == '"'){
				return VALUE;
			}else{
				return NAME;
			}
		}
	
}

Lista<string>* Parser::extraerPalabras(string* atributos){
	
	Lista<string>* palabras = new Lista<string>();

	while(atributos->length() > 0){
		string* palabra = new string();
		extraerPalabra(atributos,palabra);
		backTrim(palabra);
		frontTrim(palabra);
		palabras->agregar(palabra);
	}	

	return palabras;
}

void Parser::extraerPalabra(string* frase, string* palabra){
	
	frontTrim(frase);
	backTrim(frase);
	int indice = 0;
	char simbolos[5];

	simbolos[0] = '"';
	
	if(frase->at(0) == '"'){
		indice++;
		escaparHasta(simbolos,1,indice, frase);
	}else{
		simbolos[0] = '=';
		simbolos[1] = ' ';
		escaparHasta(simbolos,2,indice,frase);
		if(frase->at(indice) == '=' && indice != 0){
			indice--;
		}
	}

	copiarYQuitar(frase,  palabra, 0, indice);
}


/*
 * Coloca el indice de la cadena en la ultima posicion con un simbolo igual a los pasados.
 */
void Parser::escapar(char simbolos[], int cantidadSimbolos, int &indice, string* cadena){
	
	bool hayCoincidencia = true;

	while(indice < cadena->length() && hayCoincidencia){
		hayCoincidencia = false;
		for(int i = 0; i < cantidadSimbolos; i++){
			if(cadena->at(indice) == simbolos[i] ){
				hayCoincidencia = true;
			}
		}

		if(hayCoincidencia){
			indice++;
		}
	}

	if(!hayCoincidencia){
			indice--;
	}

}

/*
 * Coloca el indice de la cadena en la ultima posicion con un simbolo igual a alguno de los pasados.
 */
void Parser::escaparHasta(char simbolos[], int cantidadSimbolos, int &indice, string* cadena){
	bool hayCoincidencia = false;

	while(indice < cadena->length() && !hayCoincidencia){

		for(int i = 0; i < cantidadSimbolos; i++){
			if(cadena->at(indice) == simbolos[i] ){
				hayCoincidencia = true;
			}
		}

		if(!hayCoincidencia){
			indice++;
		}
	}

	if(!hayCoincidencia){
			indice--;
	}
}

/*
 * Quita los espacios en blanco al ppio de la cadena.
 */
void Parser::frontTrim(string* cadena){
	
	unsigned int indice = 0;

	while(indice < cadena->length() &&  cadena->at(indice) == ' '){
		cadena->replace(indice,1,"");
	}
}

/*
 * Quita los espacios en blanco al final de la cadena.
 */
void Parser::backTrim(string* cadena){
	
	unsigned int indice = cadena->length() - 1;

	while(cadena->length() > 1 && cadena->at(cadena->length() - 1) == ' '){
		cadena->replace(cadena->length() - 1,1,"");
	}
}

/*
 * Quita los espacios en blanco de la cadena.
 */
void Parser::trim(string* cadena){
	
	unsigned int indice = 0;

	while(indice < cadena->length()){
		if(cadena->at(indice) == ' '){
			cadena->replace(indice,1,"");
		}else{
			indice++;
		}
	}
	
}

/*
 * Copia una subcadena, quitandola de la original.
 */
void Parser::copiarYQuitar(string* original, string* copia, int desde, int hasta){

	while(desde < original->length() && desde <= hasta){
		(*copia) += original->at(desde);
		original->replace(desde,1,"");
		hasta--;
	}
	
}

/*
 * Borra la porcion de cadena especificada.
 */
void Parser::borrar(string* cadena, int desde, int hasta){
	
	string* copia = new string();
	copiarYQuitar(cadena, copia,desde,hasta);
	delete copia;

}

Parser::~Parser(void){
	delete this->closingTags;
	delete this->xml ;
}